package laxproxy

import (
	"context"
	"fmt"
	"github.com/panjf2000/gnet"
	"go.uber.org/zap"
	"io"
	"net"
)

// 创建用户自定久的上下文
type CreateUserContextFunctor func() IProxyUserContext

// @Desc: 总代理类
type HookProxy struct {
	// 监听的客户端端口
	listenClientPort int32
	// 为了不实现gnet所有函数,所以要用这个
	*gnet.EventServer
	// 解码器
	codec IHookCodec
	// 根节点的关闭context
	rootCtx context.Context
	// 创建用户态上下文回调
	userCtxFunctor CreateUserContextFunctor
}

// 客户端连接上来
func (hp *HookProxy) OnOpened(c gnet.Conn) (out []byte, action gnet.Action) {
	zap.S().Infof("[网络] 新连接[%s],连接端口[%d]", c.RemoteAddr().String(), hp.listenClientPort)
	// 创建默认的自定义数据
	userCtx := hp.userCtxFunctor()
	uuid := getProxyCtxMgr().RequireContext(&ProxyBaseContext{
		clientConn:              c,
		stopChan:                make(chan bool, 1),
		client2serverSocketBuff: make([]byte, 0),
		server2clientSocketBuff: make([]byte, 0),
		web2ServerBuff:          make([]byte, 0),
		userCtx:                 userCtx,
	})
	c.SetContext(uuid)
	return
}

// 接收服务器消息
func (hp *HookProxy) RecvServerMessage(cancelCtx context.Context, pc *ProxyBaseContext) {
	go func() {
		readBuf := make([]byte, 1024000)
		for {
			msgLen, err := pc.serverConn.Read(readBuf)
			if err != nil {
				if err == io.EOF || msgLen < 1 {
					pc.stopChan <- true
					break
				}
			}
			pc.server2clientSocketBuff = append(pc.server2clientSocketBuff, readBuf[:msgLen]...)
			for {
				nextMsgLen := hp.codec.CalcNextMsgLen(pc.server2clientSocketBuff)
				if nextMsgLen < 1 ||
					nextMsgLen > uint32(len(pc.server2clientSocketBuff)) {
					break
				}
				headMsg := pc.server2clientSocketBuff[:nextMsgLen]
				afterBuf := hp.codec.OnMessage(hp.codec, pc.server2clientSocketBuff[:nextMsgLen], pc.userCtx, false)
				pc.server2clientSocketBuff = pc.server2clientSocketBuff[nextMsgLen:]
				if len(afterBuf) < 1 {
					zap.S().Warnf("[消息] afterbuf长度为0")
					continue
				}
				if err = pc.clientConn.AsyncWrite(afterBuf); err != nil {
					zap.S().Warnf("[消息] 发送给客户端失败[%v]", err)
					pc.stopChan <- true
				}
				if pc.userCtx.GetRoleUid() == 0 {
					continue
				}
				if head, err := hp.codec.DecodeHead(headMsg); err == nil {
					if _, ok := genProtoMgrInstance().Protocols[head.GetProtoId()]; ok {
						genProtoMgrInstance().ServerReplyChan <- &ServerReplyNode{
							RoleUid:  pc.userCtx.GetRoleUid(),
							ProtoId:  head.GetProtoId(),
							ReplyMsg: afterBuf[head.GetHeadLen():],
						}
					}
				}
			}
		}
	}()
	defer func() {
		if pc.clientConn != nil {
			pc.clientConn.Close()
		}
		if pc.serverConn != nil {
			pc.serverConn.Close()
		}
	}()
	select {
	case <-cancelCtx.Done():
	case <-pc.stopChan:
	}
}

// 客户端断开
func (hp *HookProxy) OnClosed(c gnet.Conn, err error) (action gnet.Action) {
	zap.S().Infof("[网络] 客户端[%s]关闭", c.RemoteAddr().String())
	if uuid, ok := c.Context().(uint32); ok {
		getProxyCtxMgr().Stop(uuid)
	}
	return
}

// 收到客户端消息
func (hp *HookProxy) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) {
	if uuid, ok := c.Context().(uint32); ok {
		ctx := getProxyCtxMgr().GetContext(uuid)
		ctx.client2serverSocketBuff = append(ctx.client2serverSocketBuff, frame...)
		for {
			nextMsgLen := hp.codec.CalcNextMsgLen(ctx.client2serverSocketBuff)
			if nextMsgLen < 1 || nextMsgLen > uint32(len(ctx.client2serverSocketBuff)) {
				break
			}
			afterFrame := hp.codec.OnMessage(hp.codec, ctx.client2serverSocketBuff[:nextMsgLen], ctx.userCtx, true)
			if len(afterFrame) < 1 {
				zap.S().Error("[网络] afterframe长度为0")
				ctx.stopChan <- true
				return
			}
			ctx.client2serverSocketBuff = ctx.client2serverSocketBuff[nextMsgLen:]
			if ctx.serverConn == nil {
				// 首次发消息，获取服务器地址。
				ipaddr, err := ctx.userCtx.GetConnectServerAddress()
				if err != nil {
					if err.Error() == "Wait" {
						return
					}
					zap.S().Warnf("[网络] [%s]获取服务器地址失败", c.RemoteAddr().String())
					c.Close()
					return
				}
				zap.S().Info("[网络] 获取到服务器地址: ", ipaddr)
				conn, err := net.Dial("tcp", ipaddr)
				if err != nil {
					zap.S().Warnf("[网络] 有连接连上来[%s]但是连接gate[%s]失败,err[%v]", c.RemoteAddr().String(), ipaddr, err)
					c.Close()
					return
				}
				zap.S().Infof("[网络] 有连接连上来[%s],连接[%s]成功,本地[%s]",
					c.RemoteAddr().String(),
					ipaddr,
					conn.LocalAddr().String())
				ctx.serverConn = conn
				go hp.RecvServerMessage(hp.rootCtx, ctx)
			}
			if n, err := ctx.serverConn.Write(afterFrame); err != nil || n < 1 { //转发
				zap.S().Warnf("[网络] 发送服务器[%d]字节,err[%v]", n, err)
				ctx.stopChan <- true
			} else if len(ctx.web2ServerBuff) > 0 {
				webSendMsgMutex.Lock()
				// 发送成功,把web的也一起发送
				ctx.serverConn.Write(ctx.web2ServerBuff)
				ctx.web2ServerBuff = ctx.web2ServerBuff[:]
				webSendMsgMutex.Unlock()
			}
		}

	} else {
		zap.S().Warnf("[网络] [%s]没有上下文,非法", c.RemoteAddr().String())
		c.Close()
	}
	return
}

// @Desc: 创建一个代理并且激活
// @param ctx 进程主动关闭的上下文
// @param port 监听客户端的端口
// @param codec 编码解码器
func NewHookProxyAndServe(ctx context.Context, port int32, codec IHookCodec, userContextFunctor CreateUserContextFunctor) {
	hp := &HookProxy{
		listenClientPort: port,
		codec:            codec,
		rootCtx:          ctx,
		userCtxFunctor:   userContextFunctor,
	}
	if err := gnet.Serve(hp, fmt.Sprintf("tcp://:%d", hp.listenClientPort), gnet.WithMulticore(true)); err != nil {
		zap.S().Errorf("[网络] 监听失败[%v]", err)
		// TODO 支持重连
		panic(fmt.Sprintf("监听端口[%v]失败", err))
	}
}
